
#include "core0/kernelCircularBuffer.h"

/****************************************************************************/

int kernelMultitaskerWaitMcs(unsigned interval)
{
	// This function will put a process into the waiting state for *at least*
	// the specified number of microsecond, and yield control back to the
	// scheduler

	int status = 0;

	SANE();

	kernelPerformanceCounterInc1(pcWaitMcsCalls);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	kernelCurrentProcess->waitUntil = (kernelGetMicrosecondSoftTimer() + interval);
	kernelCurrentProcess->waitState |= WAIT_STATE_TIMER;
	kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
	RELEASE_LOCK_PROCESS_QUEUE();

//if (kernelCurrentProcess->terminationBlocked)
//kernelError(kernel_warn, "P %u, %d", kernelCurrentProcess->processId, kernelCurrentProcess->terminationBlocked);

	return 0;

}

/****************************************************************************/

int kernelMultitaskerBlock(int processId)
{
	// This function will put a process into the waiting state
	// until the requested blocking process has completed

	int status = 0;
	kernelProcess *blockProcess = NULL;

	SANE();

	kernelPerformanceCounterInc1(pcBlockCalls);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Get the process that we're supposed to block on
	blockProcess = getProcessById(processId);
	if (blockProcess == NULL)
	{
		// The process does not exist.
		RELEASE_LOCK_PROCESS_QUEUE();
		kernelError("The process on which to block does not exist");
		return (status = ERR_NOSUCHPROCESS);
	}

	// Take the text streams that belong to the current process and
	// give them to the target process
	kernelMultitaskerDuplicateIO(kernelCurrentProcess->processId, processId, 0); // don't clear

	kernelCurrentProcess->waitForProcess = processId;
	kernelCurrentProcess->waitState |= WAIT_STATE_BLOCK;
	kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
	RELEASE_LOCK_PROCESS_QUEUE();

	// Get the exit code from the process
	return (kernelCurrentProcess->blockingExitCode);
}

/****************************************************************************/

int kernelMultitaskerDetach(void)
{
	int status = 0;
	kernelProcess *parentProcess = NULL;

	// This will allow a program or daemon to detach from its parent process
	// if the parent process is blocking.

	SANE();

	ACQUIRE_LOCK_PROCESS_QUEUE();

	parentProcess = getProcessById(kernelCurrentProcess->parentProcessId);

	// Set the input/output streams to the console
	kernelMultitaskerDuplicateIO(KERNELPROCID, kernelCurrentProcess->processId, 0); // don't clear  

	// Get the process that's blocking on this one, if any
	if (parentProcess)
	{
		if (parentProcess->waitForProcess == kernelCurrentProcess->processId)
		{
			// Clear the return code of the parent process
			parentProcess->blockingExitCode = 0;

			// Clear the parent's wait for process value
			parentProcess->waitForProcess = 0;

			// Make it runnable
			kernelCurrentProcess->waitState &= ~WAIT_STATE_BLOCK;
		}
	}
	kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
	RELEASE_LOCK_PROCESS_QUEUE();

	return (status = 0);
}

/****************************************************************************/

static kernelCircularBuffer fastNotifyQueue;
static int fastNotifyQueueInitialized = 0;

/****************************************************************************/

int kernelMultitaskerWaitFastNotify()
{
	int status = 0;

	SANE();

	if (!fastNotifyQueueInitialized)
	{
		kernelCircularBufferAllocate(&fastNotifyQueue, sizeof(int) * 64);
		fastNotifyQueueInitialized = 1;
	}

	kernelPerformanceCounterInc1(pcWaitFastNotifyCalls);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	kernelCurrentProcess->waitState |= WAIT_STATE_FAST_NOTIFY;
	kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
	RELEASE_LOCK_PROCESS_QUEUE();

	return 0;

}

/****************************************************************************/

int kernelMultitaskerSendFastNotify(int processId)
{
	if (!multitaskingEnabled)
		return -1;

	kernelCircularBufferWrite(&fastNotifyQueue, &processId, sizeof(processId));

	return 0;

}

/****************************************************************************/

static void dispatchFastNotify(void)
{
	if (kernelCircularBufferGetOverrun(&fastNotifyQueue))
	{
		int count;
		for (count = 0; count < numQueued; count ++)
		{
			processQueue[count]->fastNotify = 1;
		}
		kernelCircularBufferPurge(&fastNotifyQueue);
		kernelCircularBufferDecrementOverrun(&fastNotifyQueue);
		kernelPerformanceCounterInc1(pcFastNotifyOverruns);
	}
	else
	{
		int processId;
		while (kernelCircularBufferRead(&fastNotifyQueue, &processId, sizeof(processId)) >= 0)
		{
			kernelPerformanceCounterInc1(pcFastNotifyReads);
			kernelProcess *p = getProcessById(processId);
			if (p)
				p->fastNotify = 1;
		}
	}
}

/****************************************************************************/

int kernelMultitaskerWaitEvent(void* event_buffer, int event_buffer_size, int* event_size, unsigned flags)
{
	return kernelMultitaskerWaitEventWithTimeout(event_buffer, event_buffer_size, event_size, flags, -1);
}

/****************************************************************************/

int kernelMultitaskerWaitEventWithTimeout(void* event_buffer, int event_buffer_size, int* event_size, unsigned flags, int interval)
{
	int status = 0;

	SANE();

	kernelPerformanceCounterInc1(pcWaitEventCalls);

	if (!(flags & EVENT_FLAG_NO_READ))
	{
		if (!event_buffer)
			return ERR_NULLPARAMETER;
	}

	status = -1;
	int _event_size = -1;

again: ;

//	while (1)
//	{
		ACQUIRE_LOCK_PROCESS_QUEUE();

		kernelCurrentProcess->_eventBody = NULL;

		if (kernelCurrentProcess->timeout)
		{
			kernelCurrentProcess->timeout = 0;
			status = ERR_TIMEOUT;
			goto unblock;
		}

		if (kernelCurrentProcess->unblocked)
		{
			kernelCurrentProcess->unblocked = 0;
			status = ERR_UNBLOCKED;
//			break;
			goto unblock;
		}



		if (kernelCurrentProcess->eventQueue)
		{
			if (flags & EVENT_FLAG_NO_READ)
			{
				kernelEvent * event = (kernelEvent *) kernelLinkedListGetByIndex(kernelCurrentProcess->eventQueue, 0);
				if (!event)
				{
					status = -1; // FIXME: Use reasonable status code.
				}
				else
				{
					status = 0;
					_event_size = event->size;
				}
//				break;
				goto unblock;
			}

			kernelEvent * event = (kernelEvent *) kernelLinkedListPopFirst(kernelCurrentProcess->eventQueue);
			if (event)
			{
				_event_size = event->size;
				status = 0;
				if (_event_size > event_buffer_size)
				{
					if (flags & EVENT_FLAG_CUT)
					{
						_event_size = event_buffer_size;
					}
					else
					{
						kernelLinkedListPushFirst(kernelCurrentProcess->eventQueue, event);
//						break;
						goto unblock;
					}
				}
				kernelCurrentProcess->_eventBody = event->body;
				kernelFree(event);
//				break;
				goto unblock;
			}
		}

		if (flags & EVENT_FLAG_NO_WAIT)
		{
			status = ERR_NODATA;
//			break;
			goto unblock;
		}

		kernelCurrentProcess->waitState |= WAIT_STATE_EVENT;
		if (interval >= 0)
		{
			kernelCurrentProcess->waitUntil = (kernelGetMicrosecondSoftTimer() + interval);
			kernelCurrentProcess->waitState |= WAIT_STATE_TIMER;
		}
		kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
		RELEASE_LOCK_PROCESS_QUEUE();
		goto again;
//	}

unblock: ;

	RELEASE_LOCK_PROCESS_QUEUE();

	if (_event_size >= 0 && event_size)
		*event_size = _event_size;

	if (kernelCurrentProcess->_eventBody)
	{
		kernelMemCopy(kernelCurrentProcess->_eventBody, event_buffer, _event_size);
		kernelFree(kernelCurrentProcess->_eventBody);
		kernelCurrentProcess->_eventBody = NULL;
	}

	return (status);
}

/****************************************************************************/

int kernelMultitaskerSendEvent(int processId, void* event_buffer, int event_size, unsigned flags)
{
	int status = 0;
	kernelProcess *targetProcess = NULL;

	SANE();

	kernelEvent * event = NULL;
	void * event_body = NULL;

	kernelPerformanceCounterInc1(pcSendEventCalls);
	if (flags & EVENT_FLAG_UNBLOCK)
	{
		kernelPerformanceCounterInc1(pcSendEventUnblockCalls);
	}

	if (!(flags & EVENT_FLAG_UNBLOCK))
	{
		if (event_size > 0 && event_buffer)
		{
			event_body = kernelMalloc(event_size);
			if (!event_body)
			{
				status = ERR_MEMORY;
				goto out;
			}
			kernelMemCopy(event_buffer, event_body, event_size);
		}
	}

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Get the target process
	targetProcess = getProcessById(processId);
	if (targetProcess == NULL)
	{
		status = ERR_NOSUCHPROCESS;
		goto out_locked;
	}

	if (kernelCurrentProcess->priority < targetProcess->priority - 2)
	{
		int boost = (targetProcess->priority - kernelCurrentProcess->priority) / 2;
		setBoost(targetProcess, boost);
	}

	if (flags & EVENT_FLAG_UNBLOCK)
	{
		targetProcess->unblocked = 1;
		if (targetProcess->waitState & (WAIT_STATE_EVENT | WAIT_STATE_TIMER))
		{
			targetProcess->waitState &= ~(WAIT_STATE_EVENT | WAIT_STATE_TIMER);
			kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
		}
		goto out_locked;
	}

	if (event_size < 0)
	{
		status = ERR_INVALID;
		goto out_locked;
	}

	if (event_size > 0 && !event_buffer)
	{
		status = ERR_NULLPARAMETER;
		goto out_locked;
	}

	if (!targetProcess->eventQueue)
	{
		targetProcess->eventQueue = (kernelLinkedList *) kernelMalloc(sizeof(kernelLinkedList));
		if (!targetProcess->eventQueue)
		{
			status = ERR_MEMORY;
			goto out_locked;
		}
	}

	event = (kernelEvent *) kernelMalloc(sizeof(kernelEvent));
	if (!event)
	{
		status = ERR_MEMORY;
		goto out_locked;
	}

	event->size = event_size;
	event->body = event_body;

	status = kernelLinkedListPushLast(targetProcess->eventQueue, event);
	if (status)
	{
		goto out_locked;
	}

	event = NULL;
	event_body = NULL;

	if (targetProcess->waitState & WAIT_STATE_EVENT)
	{
		targetProcess->waitState &= ~WAIT_STATE_EVENT;
		kernelMultitaskerDispatch(DISPATCH_RESCHEDULE);
	}

out_locked:
	RELEASE_LOCK_PROCESS_QUEUE();
out:
	if (event)
		kernelFree(event);
	if (event_body)
		kernelFree(event_body);
	if (status == ERR_NOSUCHPROCESS)
		kernelError("The target process does not exist");
	else if (status == ERR_MEMORY)
		kernelError("Memory allocation error");
	return (status);
}

/****************************************************************************/
